<?php
/**
 * Zend Framework
 * LICENSE
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 * @category Zend
 * @package Zend_Filter
 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
 * @license http://framework.zend.com/license/new-bsd New BSD License
 * @version $Id: Zip.php 24593 2012-01-05 20:35:02Z matthew $
 */
/**
 *
 * @see Zend_Filter_Compress_CompressAbstract
 */
require_once 'Zend/Filter/Compress/CompressAbstract.php';

/**
 * Compression adapter for zip
 * @category Zend
 * @package Zend_Filter
 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
 * @license http://framework.zend.com/license/new-bsd New BSD License
 */
class Zend_Filter_Compress_Zip extends Zend_Filter_Compress_CompressAbstract {

    /**
     * Compression Options
     * array(
     * 'archive' => Archive to use
     * 'password' => Password to use
     * 'target' => Target to write the files to
     * )
     * @var array
     */
    protected $_options = array('archive' => null, 'target' => null);

    /**
     * Class constructor
     * @param string|array $options (Optional) Options to set
     */
    public function __construct ($options = null) {
        if ( ! extension_loaded ('zip')) {
            require_once 'Zend/Filter/Exception.php';
            throw new Zend_Filter_Exception ('This filter needs the zip extension');
        }
        parent::__construct ($options);
    }

    /**
     * Returns the set archive
     * @return string
     */
    public function getArchive () {
        return $this -> _options['archive'];
    }

    /**
     * Sets the archive to use for de-/compression
     * @param string $archive Archive to use
     * @return Zend_Filter_Compress_Rar
     */
    public function setArchive ($archive) {
        $archive = str_replace (array('/', '\\'), DIRECTORY_SEPARATOR, $archive);
        $this -> _options['archive'] = (string) $archive;
        return $this;
    }

    /**
     * Returns the set targetpath
     * @return string
     */
    public function getTarget () {
        return $this -> _options['target'];
    }

    /**
     * Sets the target to use
     * @param string $target
     * @return Zend_Filter_Compress_Rar
     */
    public function setTarget ($target) {
        if ( ! file_exists (dirname ($target))) {
            require_once 'Zend/Filter/Exception.php';
            throw new Zend_Filter_Exception ("The directory '$target' does not exist");
        }
        $target = str_replace (array('/', '\\'), DIRECTORY_SEPARATOR, $target);
        $this -> _options['target'] = (string) $target;
        return $this;
    }

    /**
     * Compresses the given content
     * @param string $content
     * @return string Compressed archive
     */
    public function compress ($content) {
        $zip = new ZipArchive ();
        $res = $zip -> open ($this -> getArchive (), ZipArchive::CREATE | ZipArchive::OVERWRITE);
        if ($res !== true) {
            require_once 'Zend/Filter/Exception.php';
            throw new Zend_Filter_Exception ($this -> _errorString ($res));
        }
        if (file_exists ($content)) {
            $content = str_replace (array('/', '\\'), DIRECTORY_SEPARATOR, realpath ($content));
            $basename = substr ($content, strrpos ($content, DIRECTORY_SEPARATOR) + 1);
            if (is_dir ($content)) {
                $index = strrpos ($content, DIRECTORY_SEPARATOR) + 1;
                $content .= DIRECTORY_SEPARATOR;
                $stack = array($content);
                while ( ! empty ($stack)) {
                    $current = array_pop ($stack);
                    $files = array();
                    $dir = dir ($current);
                    while (false !== ($node = $dir -> read ())) {
                        if (($node == '.') || ($node == '..')) {
                            continue;
                        }
                        if (is_dir ($current . $node)) {
                            array_push ($stack, $current . $node . DIRECTORY_SEPARATOR);
                        }
                        if (is_file ($current . $node)) {
                            $files[] = $node;
                        }
                    }
                    $local = substr ($current, $index);
                    $zip -> addEmptyDir (substr ($local, 0,  - 1));
                    foreach ($files as $file) {
                        $zip -> addFile ($current . $file, $local . $file);
                        if ($res !== true) {
                            require_once 'Zend/Filter/Exception.php';
                            throw new Zend_Filter_Exception ($this -> _errorString ($res));
                        }
                    }
                }
            } else {
                $res = $zip -> addFile ($content, $basename);
                if ($res !== true) {
                    require_once 'Zend/Filter/Exception.php';
                    throw new Zend_Filter_Exception ($this -> _errorString ($res));
                }
            }
        } else {
            $file = $this -> getTarget ();
            if ( ! is_dir ($file)) {
                $file = basename ($file);
            } else {
                $file = "zip.tmp";
            }
            $res = $zip -> addFromString ($file, $content);
            if ($res !== true) {
                require_once 'Zend/Filter/Exception.php';
                throw new Zend_Filter_Exception ($this -> _errorString ($res));
            }
        }
        $zip -> close ();
        return $this -> _options['archive'];
    }

    /**
     * Decompresses the given content
     * @param string $content
     * @return string
     */
    public function decompress ($content) {
        $archive = $this -> getArchive ();
        if (file_exists ($content)) {
            $archive = str_replace (array('/', '\\'), DIRECTORY_SEPARATOR, realpath ($content));
        } elseif (empty ($archive) ||  ! file_exists ($archive)) {
            require_once 'Zend/Filter/Exception.php';
            throw new Zend_Filter_Exception ('ZIP Archive not found');
        }
        $zip = new ZipArchive ();
        $res = $zip -> open ($archive);
        $target = $this -> getTarget ();
        if ( ! empty ($target) &&  ! is_dir ($target)) {
            $target = dirname ($target);
        }
        if ( ! empty ($target)) {
            $target = rtrim ($target, '/\\') . DIRECTORY_SEPARATOR;
        }
        if (empty ($target) ||  ! is_dir ($target)) {
            require_once 'Zend/Filter/Exception.php';
            throw new Zend_Filter_Exception ('No target for ZIP decompression set');
        }
        if ($res !== true) {
            require_once 'Zend/Filter/Exception.php';
            throw new Zend_Filter_Exception ($this -> _errorString ($res));
        }
        if (version_compare (PHP_VERSION, '5.2.8', '<')) {
            for ($i = 0; $i < $zip -> numFiles; $i ++ ) {
                $statIndex = $zip -> statIndex ($i);
                $currName = $statIndex['name'];
                if (($currName{0} == '/') || (substr ($currName, 0, 2) == '..') || (substr ($currName, 0, 4) == './..')) {
                    require_once 'Zend/Filter/Exception.php';
                    throw new Zend_Filter_Exception ('Upward directory traversal was detected inside ' . $archive . ' please use PHP 5.2.8 or greater to take advantage of path resolution features of ' . 'the zip extension in this decompress() method.');
                }
            }
        }
        $res = @$zip -> extractTo ($target);
        if ($res !== true) {
            require_once 'Zend/Filter/Exception.php';
            throw new Zend_Filter_Exception ($this -> _errorString ($res));
        }
        $zip -> close ();
        return $target;
    }

    /**
     * Returns the proper string based on the given error constant
     * @param string $error
     */
    protected function _errorString ($error) {
        switch ($error) {
            case ZipArchive::ER_MULTIDISK :
                return 'Multidisk ZIP Archives not supported';
            case ZipArchive::ER_RENAME :
                return 'Failed to rename the temporary file for ZIP';
            case ZipArchive::ER_CLOSE :
                return 'Failed to close the ZIP Archive';
            case ZipArchive::ER_SEEK :
                return 'Failure while seeking the ZIP Archive';
            case ZipArchive::ER_READ :
                return 'Failure while reading the ZIP Archive';
            case ZipArchive::ER_WRITE :
                return 'Failure while writing the ZIP Archive';
            case ZipArchive::ER_CRC :
                return 'CRC failure within the ZIP Archive';
            case ZipArchive::ER_ZIPCLOSED :
                return 'ZIP Archive already closed';
            case ZipArchive::ER_NOENT :
                return 'No such file within the ZIP Archive';
            case ZipArchive::ER_EXISTS :
                return 'ZIP Archive already exists';
            case ZipArchive::ER_OPEN :
                return 'Can not open ZIP Archive';
            case ZipArchive::ER_TMPOPEN :
                return 'Failure creating temporary ZIP Archive';
            case ZipArchive::ER_ZLIB :
                return 'ZLib Problem';
            case ZipArchive::ER_MEMORY :
                return 'Memory allocation problem while working on a ZIP Archive';
            case ZipArchive::ER_CHANGED :
                return 'ZIP Entry has been changed';
            case ZipArchive::ER_COMPNOTSUPP :
                return 'Compression method not supported within ZLib';
            case ZipArchive::ER_EOF :
                return 'Premature EOF within ZIP Archive';
            case ZipArchive::ER_INVAL :
                return 'Invalid argument for ZLIB';
            case ZipArchive::ER_NOZIP :
                return 'Given file is no zip archive';
            case ZipArchive::ER_INTERNAL :
                return 'Internal error while working on a ZIP Archive';
            case ZipArchive::ER_INCONS :
                return 'Inconsistent ZIP archive';
            case ZipArchive::ER_REMOVE :
                return 'Can not remove ZIP Archive';
            case ZipArchive::ER_DELETED :
                return 'ZIP Entry has been deleted';
            default :
                return 'Unknown error within ZIP Archive';
        }
    }

    /**
     * Returns the adapter name
     * @return string
     */
    public function toString () {
        return 'Zip';
    }

}
